package com.gitee.cliveyuan.tools.web;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.cliveyuan.tools.Assert;
import com.gitee.cliveyuan.tools.IdTools;
import com.gitee.cliveyuan.tools.StringTools;
import com.gitee.cliveyuan.tools.VerifyCodeTools;
import com.gitee.cliveyuan.tools.bean.Captcha;
import com.gitee.cliveyuan.tools.bean.CaptchaCodeResult;
import com.gitee.cliveyuan.tools.bean.CaptchaRequest;
import com.gitee.cliveyuan.tools.bean.CaptchaV2;
import com.gitee.cliveyuan.tools.bean.CaptchaValidate;
import com.gitee.cliveyuan.tools.enums.CaptchaGenerateType;
import com.gitee.cliveyuan.tools.enums.CaptchaType;
import com.gitee.cliveyuan.tools.exception.CaptchaException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Objects;


/**
 * 验证码工具类
 *
 * @author clive
 * Created on 2018-07-27
 */
public class CaptchaTools {

    private final static Logger logger = LoggerFactory.getLogger(CaptchaTools.class);

    public final static String DEFAULT_CAPTCHA_NAME = "CAPTCHA_NAME";
    public final static int DEFAULT_CAPTCHA_LENGTH = 5;
    public final static int DEFAULT_CAPTCHA_WIDTH = 300;
    public final static int DEFAULT_CAPTCHA_HEIGHT = 100;

    private CaptchaTools() {
    }

    /**
     * 验证验证码
     * 验证码正确不抛异常, 错误抛CaptchaException异常
     *
     * @param inputCaptcha 输入的验证码
     * @param request      请求
     * @deprecated CaptchaTools#validate(CaptchaValidate captchaValidate)
     */
    @Deprecated
    public static void validate(Captcha inputCaptcha, HttpServletRequest request) {
        validate(inputCaptcha, request, DEFAULT_CAPTCHA_NAME);
    }

    /**
     * 验证验证码
     * 验证码正确不抛异常, 错误抛CaptchaException异常
     *
     * @param inputCaptcha 输入的验证码
     * @param request      请求
     * @param captchaName  验证码session名
     * @see CaptchaTools#validate(CaptchaValidate captchaValidate)
     * @deprecated CaptchaTools#validate(CaptchaValidate captchaValidate)
     */
    @Deprecated
    public static void validate(Captcha inputCaptcha, HttpServletRequest request, String captchaName) {
        logger.debug("CaptchaTools.validate: inputCaptcha={}, captchaName={}", inputCaptcha, captchaName);
        Assert.notNull(request, "request is required");
        HttpSession session = request.getSession();
        if (Objects.isNull(inputCaptcha)) throw CaptchaException.captchaIsNull();
        if (StringTools.isBlank(inputCaptcha.getCaptchaId())) throw CaptchaException.idIsEmpty();
        if (StringTools.isBlank(inputCaptcha.getCaptchaValue())) throw CaptchaException.valueIsEmpty();
        if (StringTools.isBlank(captchaName)) captchaName = DEFAULT_CAPTCHA_NAME;

        inputCaptcha.setCaptchaValue(inputCaptcha.getCaptchaValue().toLowerCase());
        String captchaStr = (String) session.getAttribute(captchaName);
        if (StringTools.isBlank(captchaStr)) throw CaptchaException.notInSession();

        Captcha captcha = JSONObject.parseObject(captchaStr, Captcha.class);
        if (Objects.isNull(captcha)) throw CaptchaException.parseJsonError();
        logger.debug("CaptchaTools.validate:  输入的验证码: {}, 生成的验证码: {}", inputCaptcha, captcha);
        if (!Objects.equals(inputCaptcha, captcha)) throw CaptchaException.notMatch();
        logger.debug("CaptchaTools.validate: matched");
    }

    /**
     * 校验验证码 v2
     * <p>
     * 验证码正确不抛异常, 错误抛CaptchaException异常
     *
     * @param captchaValidate
     */
    public static boolean validate(CaptchaValidate captchaValidate) {
        logger.debug("CaptchaTools.validate: captchaValidate={}", captchaValidate);

        try {
            Assert.notNull(captchaValidate, "captchaValidate is null");

            HttpServletRequest request = captchaValidate.getRequest();
            CacheManager cacheManager = captchaValidate.getCacheManager();
            String cacheNamespace = captchaValidate.getCacheNamespace();

            Assert.notNull(captchaValidate.getGenerateType(), "generateType is null");
            if (CaptchaGenerateType.SESSION.equals(captchaValidate.getGenerateType())) {
                Assert.notNull(request, "request is null");
            } else if (CaptchaGenerateType.CACHE.equals(captchaValidate.getGenerateType())) {
                Assert.notNull(cacheManager, "cacheManager is null");
                Assert.notNull(cacheNamespace, "cacheNamespace is null");
            }

            CaptchaV2 inputCaptcha = captchaValidate.getCaptcha();
            String captchaName = captchaValidate.getName();
            if (Objects.isNull(inputCaptcha))
                throw CaptchaException.captchaIsNull();
            if (StringTools.isBlank(inputCaptcha.getId()))
                throw CaptchaException.idIsEmpty();
            if (StringTools.isBlank(inputCaptcha.getValue()))
                throw CaptchaException.valueIsEmpty();
            if (StringTools.isBlank(captchaName))
                captchaName = DEFAULT_CAPTCHA_NAME;

            inputCaptcha.setValue(inputCaptcha.getValue().toLowerCase());

            CaptchaV2 captcha = null;
            if (CaptchaGenerateType.SESSION.equals(captchaValidate.getGenerateType())) {
                captcha = getFromSession(captchaValidate, captchaName);
            } else if (CaptchaGenerateType.CACHE.equals(captchaValidate.getGenerateType())) {
                captcha = getFromCache(captchaValidate, captchaName);
            }

            if (Objects.isNull(captcha))
                throw CaptchaException.parseJsonError();
            logger.debug("CaptchaTools.validate:  输入的验证码: {}, 生成的验证码: {}", inputCaptcha, captcha);

            // 移除验证码
            removeCaptcha(captchaValidate, captchaName);
            captcha.setValue(captcha.getValue().toLowerCase()); // fix 大小写不匹配问题
            if (!Objects.equals(inputCaptcha, captcha)) {
                throw CaptchaException.notMatch();
            }
        } catch (CaptchaException e) {
            if (captchaValidate.isThrowExceptionWhenError()) {
                throw e;
            }
            logger.info("CaptchaTools.validate: ERROR  code={}, msg={}", e.getCode(), e.getMessage());
            return false;
        }

        logger.debug("CaptchaTools.validate: matched");

        return true;
    }

    /**
     * 生成验证码
     *
     * @param request  请求
     * @param response 响应
     * @param id       验证码id 初始生成时可不传
     */
    @Deprecated
    public static void generate(
            HttpServletRequest request,
            HttpServletResponse response,
            String id
    ) {
        generate(request, response, id, DEFAULT_CAPTCHA_NAME);
    }

    /**
     * 生成验证码
     *
     * @param request     请求
     * @param response    响应
     * @param id          验证码id 初始生成时可不传
     * @param captchaName 验证码session名
     */
    @Deprecated
    public static void generate(
            HttpServletRequest request,
            HttpServletResponse response,
            String id,
            String captchaName
    ) {
        generate(request, response, id, captchaName, DEFAULT_CAPTCHA_LENGTH);
    }

    /**
     * 生成验证码
     *
     * @param request     请求
     * @param response    响应
     * @param id          验证码id 初始生成时可不传
     * @param captchaName 验证码session名
     * @param length      验证码字符长度
     */
    @Deprecated
    public static void generate(
            HttpServletRequest request,
            HttpServletResponse response,
            String id,
            String captchaName,
            int length
    ) {
        generate(request, response, id, captchaName, length, DEFAULT_CAPTCHA_WIDTH, DEFAULT_CAPTCHA_HEIGHT);
    }

    /**
     * 生成验证码
     *
     * @param request     请求
     * @param response    响应
     * @param id          验证码id 初始生成时可不传
     * @param captchaName 验证码session名
     * @param length      验证码字符长度
     * @param width       验证码图片宽度 单位像素
     * @param height      验证码图片搞定 单位像素
     * @see CaptchaTools#generate(CaptchaRequest captchaRequest)
     * @deprecated see CaptchaTools#generate(CaptchaRequest captchaRequest)
     */
    @Deprecated
    public static void generate(
            HttpServletRequest request,
            HttpServletResponse response,
            String id,
            String captchaName,
            int length,
            int width,
            int height
    ) {
        logger.debug("CaptchaTools.generate id={}, length={}", id, length);
        try {
            System.setProperty("java.awt.headless", "true");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setContentType("image/jpeg");
            // 生成随机字串
            String verifyCode = VerifyCodeTools.generateVerifyCode(length);
            // 存入会话session
            HttpSession session = request.getSession(true);
            if (StringTools.isBlank(id)) {
                id = IdTools.randomShortUUID();
            }
            Captcha captcha = new Captcha(id, verifyCode.toLowerCase());
            session.setAttribute(captchaName, JSON.toJSONString(captcha));
            // 生成图片
            VerifyCodeTools.outputImage(width, height, response.getOutputStream(), verifyCode);
            logger.debug("CaptchaTools.generate: success -> {}", captcha);
        } catch (Exception e) {
            logger.error("generate error", e);
        }
    }

    /**
     * 生成验证码 v2
     *
     * @param captchaRequest captchaRequest
     */
    public static void generate(CaptchaRequest captchaRequest) {
        logger.debug("CaptchaTools.generate captchaRequest={}", captchaRequest);

        Assert.notNull(captchaRequest, "captchaRequest is null");

        HttpServletResponse response = captchaRequest.getResponse();
        HttpServletRequest request = captchaRequest.getRequest();
        CacheManager cacheManager = captchaRequest.getCacheManager();
        String cacheNamespace = captchaRequest.getCacheNamespace();

        Assert.notNull(response, "response is null");
        Assert.notNull(captchaRequest.getGenerateType(), "generateType is null");
        if (CaptchaGenerateType.SESSION.equals(captchaRequest.getGenerateType())) {
            Assert.notNull(request, "request is null");
        } else if (CaptchaGenerateType.CACHE.equals(captchaRequest.getGenerateType())) {
            Assert.notNull(cacheManager, "cacheManager is null");
            Assert.notNull(cacheNamespace, "cacheNamespace is null");
        }
        int length = captchaRequest.getLength();
        int width = captchaRequest.getWidth();
        int height = captchaRequest.getHeight();
        if (length <= 0) {
            length = DEFAULT_CAPTCHA_LENGTH;
        }
        if (width <= 0) {
            width = DEFAULT_CAPTCHA_WIDTH;
        }
        if (height <= 0) {
            height = DEFAULT_CAPTCHA_HEIGHT;
        }

        try {
            System.setProperty("java.awt.headless", "true");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setContentType("image/jpeg");
            // 生成随机字串
            CaptchaCodeResult captchaCodeResult = CaptchaCodeResult.builder().build();
            switch (captchaRequest.getCaptchaType()) {
                case LETTER_NUM:
                    captchaCodeResult = VerifyCodeTools.generateLetterNumVerifyCode(length);
                    break;
                case MATH_ADD:
                    captchaCodeResult = VerifyCodeTools.generateMathAddVerifyCode(captchaRequest.getMathAddStart(),
                            captchaRequest.getMathAddEnd());
                    break;
            }
            String id = captchaRequest.getId();
            if (StringTools.isBlank(id)) {
                id = IdTools.randomShortUUID();
            }
            CaptchaV2 captcha = new CaptchaV2(id, captchaCodeResult.getVerifyContent());
            if (CaptchaGenerateType.SESSION.equals(captchaRequest.getGenerateType())) {
                saveToSession(captchaRequest, captcha);
            } else if (CaptchaGenerateType.CACHE.equals(captchaRequest.getGenerateType())) {
                saveToCache(captchaRequest, captcha);
            }
            // 生成图片
            VerifyCodeTools.outputImage(width, height,
                    response.getOutputStream(), captchaCodeResult.getCaptchaContent(), !CaptchaType.MATH_ADD.equals(captchaRequest.getCaptchaType()));
            logger.debug("CaptchaTools.generate: success -> {}", captcha);
        } catch (Exception e) {
            logger.error("generate error", e);
        }
    }

    private static String getKey(String captchaName, String id) {
        return String.format("%s_%s", captchaName, id);
    }

    private static void saveToSession(CaptchaRequest captchaRequest, CaptchaV2 captcha) {
        HttpSession session = captchaRequest.getRequest().getSession(true);
        String name = captchaRequest.getName();
        if (Objects.isNull(name)) {
            name = DEFAULT_CAPTCHA_NAME;
        }
        session.setAttribute(getKey(name, captcha.getId()), JSON.toJSONString(captcha));
    }

    private static void saveToCache(CaptchaRequest captchaRequest, CaptchaV2 captcha) {
        CacheManager cacheManager = captchaRequest.getCacheManager();
        Cache cache = cacheManager.getCache(captchaRequest.getCacheNamespace());
        String name = captchaRequest.getName();
        if (Objects.isNull(name)) {
            name = DEFAULT_CAPTCHA_NAME;
        }
        cache.put(getKey(name, captcha.getId()), captcha);
    }

    private static CaptchaV2 getFromSession(CaptchaValidate captchaValidate, String captchaName) {
        HttpSession session = captchaValidate.getRequest().getSession(true);
        String captchaStr = (String) session.getAttribute(getKey(captchaName, captchaValidate.getCaptcha().getId()));
        if (StringTools.isBlank(captchaStr))
            throw CaptchaException.notInSession();
        return JSONObject.parseObject(captchaStr, CaptchaV2.class);
    }

    private static CaptchaV2 getFromCache(CaptchaValidate captchaValidate, String captchaName) {
        CacheManager cacheManager = captchaValidate.getCacheManager();
        Cache cache = cacheManager.getCache(captchaValidate.getCacheNamespace());
        Cache.ValueWrapper valueWrapper = cache.get(getKey(captchaName, captchaValidate.getCaptcha().getId()));
        if (Objects.nonNull(valueWrapper)) {
            return (CaptchaV2) valueWrapper.get();
        }
        return null;
    }

    private static void removeCaptcha(CaptchaValidate captchaValidate, String captchaName) {
        if (CaptchaGenerateType.SESSION.equals(captchaValidate.getGenerateType())) {
            removeFromSession(captchaValidate, captchaName);
        } else if (CaptchaGenerateType.CACHE.equals(captchaValidate.getGenerateType())) {
            removeFromCache(captchaValidate, captchaName);
        }
    }

    private static void removeFromSession(CaptchaValidate captchaValidate, String captchaName) {
        HttpSession session = captchaValidate.getRequest().getSession(true);
        session.removeAttribute(getKey(captchaName, captchaValidate.getCaptcha().getId()));
    }

    private static void removeFromCache(CaptchaValidate captchaValidate, String captchaName) {
        CacheManager cacheManager = captchaValidate.getCacheManager();
        Cache cache = cacheManager.getCache(captchaValidate.getCacheNamespace());
        cache.evict(getKey(captchaName, captchaValidate.getCaptcha().getId()));
    }

}
